import 'dart:math';
import 'package:flutter/widgets.dart';
import 'package:get/get.dart';

import '../../../lib_runtime/logger.dart';

///
mixin RoomOverlayDraggableMixin<T extends StatefulWidget> on State<T> {
  var _minTop = 0.0;
  var _maxTop = 0.0;
  var _minLeft = 0.0;
  var _maxLeft = 0.0;

  var _left = 0.0;
  var _top = 0.0;

  static var lastLeft = 0.0;
  static var lastTop = 0.0;

  var _dx = 0.0;
  var _dy = 0.0;

  var _startX = 0.0;
  var _startY = 0.0;

  var _maxWidth = 0.0;
  var _maxHeight = 0.0;

  bool _moved = false;
  Animation<Offset>? _positionAnimation;
  AnimationController? _positionAnimationController;

  Duration longAnimationDuration = const Duration(milliseconds: 800);
  Duration shortAnimationDuration = const Duration(milliseconds: 150);

  @override
  void initState() {
    RLog.d('DraggableOverlay init state...');
    super.initState();
    initPosition();
  }

  void initPosition() {
    if (_maxWidth > 0) return;

    _maxWidth = Get.width;
    _maxHeight = Get.height;

    _minLeft = 8.0;
    _maxLeft = _maxWidth - childWidth - _minLeft;

    _minTop = 60.0;
    _maxTop = _maxHeight - childHeight - 50;

    var defaultLeft = _maxLeft;
    var defaultTop = _maxHeight - 195 - childHeight;

    _left = (lastLeft > 0 ? lastLeft : defaultLeft);
    _top = (lastTop > 0 ? lastTop : defaultTop);
  }

  void onAnimation() {
    if (mounted) setState(() {});
  }

  @override
  void dispose() {
    _positionAnimation?.removeListener(onAnimation);
    _positionAnimation = null;
    _positionAnimationController?.dispose();
    super.dispose();
  }

  void _onPointerDown(PointerDownEvent event) {
    _startX = event.position.dx;
    _startY = event.position.dy;
    RLog.d(
        'DraggableOverlay onPointerDown, _startX=$_startX, _startY=$_startY');
    _moved = false;
  }

  void _onPointerMove(PointerMoveEvent event) {
    RLog.d(
        'DraggableOverlay onPointerMove, dx=${event.position.dx}, dy=${event.position.dy}');

    double dx = event.position.dx - _startX;
    double dy = event.position.dy - _startY;

    _moved = sqrt(dx * dx + dy * dy) > 3.0;

    if (mounted) {
      _dx = dx;
      _dy = dy;
      lastLeft = _left + _dx;
      lastTop = _top + _dy;

      setState(() {});
    }
  }

  void _checkPosition() {
    double dx = 0;
    double dy = 0;

    Rect curRect =
        Rect.fromLTRB(_left, _top, _left + childWidth, _top + childHeight);

    Offset friendsPlayingOffset = Offset(_maxWidth, _maxHeight);
    Rect disallowedRect = Rect.fromLTRB(friendsPlayingOffset.dx,
        friendsPlayingOffset.dy, _maxWidth, _maxHeight);
    if (disallowedRect.overlaps(curRect)) {
      Offset curCenter =
          Offset(_left + (childWidth / 2.0), _top + (childHeight / 2.0));
      Offset disallowedCenter = disallowedRect.center;

      Offset topCenter = disallowedCenter.translate(
          0, -(childHeight / 2.0) - (disallowedRect.height / 2.0) - 5);
      Offset leftCenter = disallowedCenter.translate(
          -(childWidth / 2.0) - (disallowedRect.width / 2.0) - 5, 0);

      double topDx = topCenter.dx - curCenter.dx;
      double topDy = topCenter.dy - curCenter.dy;

      double leftDx = leftCenter.dx - curCenter.dx;
      double leftDy = leftCenter.dy - curCenter.dy;

      double topDis = topDx * topDx + topDy * topDy;
      double leftDis = leftDx * leftDx + leftDy * leftDy;

      if (topDis > leftDis) {
        dx = leftDx;
        if (_top < _minTop) {
          dy = _minTop - _top;
        } else if (_top > _maxTop) {
          dy = _maxTop - _top;
        }
      } else {
        dy = topDy;
        if (_left < _minLeft) {
          dx = _minLeft - _left;
        } else if (_left > _maxLeft) {
          dx = _maxLeft - _left;
        }
      }
    } else {
      if (_left < (_maxWidth / 2 - childWidth / 2)) {
        dx = _minLeft - _left;
      } else {
        dx = _maxLeft - _left;
      }

      if (_top < _minTop) {
        dy = _minTop - _top;
      } else if (_top > _maxTop) {
        dy = _maxTop - _top;
      }
    }

    RLog.d(
        'RoomFloating Pointer up, dx:$dx, _dx:$_dx, _left:$_left, _maxLeft: $_maxLeft');

    if (dx != 0 || dy != 0) {
      _positionAnimationController = AnimationController(
          duration: shortAnimationDuration, vsync: tickerProvider);
      _positionAnimation = _positionAnimationController!
          .drive(Tween<Offset>(begin: Offset(_dx, _dy), end: Offset(dx, dy)))
        ..addListener(onAnimation)
        ..addStatusListener((status) {
          RLog.d('Position Animation status changed: $status');
          if (status == AnimationStatus.completed && mounted) {
            setState(() {
              _dx = 0;
              _left = _left + dx;
              lastLeft = _left;

              _dy = 0;
              _top = _top + dy;
              lastTop = _top;

              _moved = false;
              _positionAnimation = null;
            });
          }
        });
      _positionAnimationController!.forward();
    } else {
      _moved = false;
    }
  }

  void _onPointerUp(PointerUpEvent event) {
    RLog.d('DraggableOverlay onPointerUp, _moved=$_moved');
    _cancel(event);
    if (_moved == false) {
      onChildTap();
    } else {
      _checkPosition();
    }
  }

  void _onPointerCancel(PointerCancelEvent event) {
    _moved = false;
    _cancel(event);
  }

  void _cancel(PointerEvent event) {
    _left += _dx;
    _top += _dy;
    _dx = 0.0;
    _dy = 0.0;
  }

  @override
  Widget build(BuildContext context) {
    double _xOffset = _positionAnimation?.value.dx ?? 0;
    double _yOffset = _positionAnimation?.value.dy ?? 0;

    return PositionedDirectional(
      start: _left + _dx + _xOffset,
      top: _top + _dy + _yOffset,
      width: childWidth,
      height: childHeight,
      child: Listener(
        onPointerDown: _onPointerDown,
        onPointerMove: _onPointerMove,
        onPointerUp: _onPointerUp,
        onPointerCancel: _onPointerCancel,
        behavior: HitTestBehavior.translucent,
        child: child,
      ),
    );
  }

  Widget get child;

  double get childWidth;

  double get childHeight;

  bool get show => true;

  TickerProvider get tickerProvider;

  void onChildTap() {}
}
